<?php

namespace DeCMF\Core;

/**
 * 验证器
 * 将原Think\Model中的校验部分拆分出来,可以具有更强的独立性
 * 
 * $validator = (new Validator)->setData($data)->setRules($rules)->validate($is_batch);
 * if ($validator->hasError()) {
 *      var_dump($validator->errors());
 * }
 * 
 * 独立的用法:
 * $allowed = (new Validator)->allowIP($ip, $allowed_ips);
 * $is_email = (new Validator)->email($email);
 */
class Validator
{

    /**
     * 正则判断
     */
    const REGEX = 'regex';

    /**
     * 在多个给定值中存在
     */
    const IN = 'in';

    /**
     * 在给定值之外
     */
    const NOT_IN = 'notin';

    /**
     * 在两数值之间
     */
    const BETWEEN = 'between';

    /**
     * 在两数值之外
     */
    const NOT_BETWEEN = 'notbetween';

    /**
     * 判断值是否相等
     */
    const EQUAL = 'equal';

    /**
     * 判断值是否不等
     */
    const NOT_EQUAL = 'notequal';

    /**
     * 判断值长度范围是否匹配 10 这样的格式代表定长  10,30 这样的格式代表长度范围
     */
    const LENGTH = 'length';

    /**
     * 日期时间是否过期
     */
    const EXPIRED = 'expire';

    /**
     * 当前IP在允许的ip范围内
     */
    const ALLOW_IP = 'ip_allow';

    /**
     * 当前IP在允许的ip范围外
     */
    const DENY_IP = 'ip_deny';
    
    const EMAIL = 'email';
    
    const REQUIRED = 'require';
    
    const URL = 'url';
    
    const NUMBER = 'number';
    
    const ZIP_CODE = 'zip';
    
    const INTEGER = 'integer';
    
    const DOUBLE = 'double';
    
    const ENGLISH = 'english';
    
    const CURRENCY = 'currency';
    
    const MOBILE = 'mobile';

    /**
     *
     * @var array 内置正则表达式校验规则简称
     */
    protected static $builtinRegexes = [
        'require' => '/\S+/',
        self::EMAIL => '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/',
        self::URL => '/^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(:\d+)?(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/',
        'currency' => '/^\d+(\.\d+)?$/',
        'number' => '/^\d+$/',
        'zip' => '/^\d{6}$/',
        'integer' => '/^[-\+]?\d+$/',
        'double' => '/^[-\+]?\d+(\.\d+)?$/',
        'english' => '/^[A-Za-z]+$/',
        self::MOBILE => '/^1[34578]{1}\d{9}$/',
    ];

    /**
     * 验证数据 支持 in between equal length regex expire ip_allow ip_deny
     *
     * @param string $value 验证数据
     * @param mixed $rule 验证表达式
     * @param string $type 验证方式 默认为正则验证
     * @return boolean
     */
    public function check($value, $rule, $type = self::REGEX)
    {
        $type = strtolower(trim($type));
        switch ($type) {
            case static::IN://  逗号分隔字符串或者数组
                return $this->in($value, $rule);
            case static::NOT_IN:
                return $this->notIn($value, $rule);
            case static::BETWEEN:// 验证是否在某个范围
                return $this->between($value, $rule);
            case static::NOT_BETWEEN:    // 验证是否不在某个范围
                return $this->notBetween($value, $rule);
            case static::EQUAL:// 验证是否等于某个值
                return $this->equal($value, $rule);
            case static::NOT_EQUAL:    // 验证是否等于某个值
                return $this->notEqual($value, $rule);
            case static::LENGTH:    // 验证长度
                return $this->length($value, $rule);
            case static::EXPIRED:
                return $this->expired($value, $rule);
            case static::ALLOW_IP:    // IP 操作许可验证
                return $this->allowIP($value, $rule);
            case static::DENY_IP:    // IP 操作禁止验证
                return $this->denyIP($value, $rule);
            case static::EMAIL:
                return $this->email($value);
            case static::URL:
                return $this->url($value);
            case static::MOBILE:
                return $this->mobile($value);
            case static::REGEX:
            default:    // 默认使用正则验证 可以使用验证类中定义的验证名称
                // 检查附加规则
                return $this->regex($value, $rule);
        }
    }

    /**
     * 验证长度
     * @param string $value
     * @param string $rule
     * @return boolean
     */
    public function length($value, $rule)
    {
        $length = mb_strlen($value, 'utf-8');     // 当前数据长度
        if (strpos($rule, ',')) {
            // 长度区间
            list($min, $max) = explode(',', $rule);
            return $length >= $min && $length <= $max;
        }

        // 指定长度
        return $length == $rule;
    }

    /**
     * 验证是否等于某个值
     * @param string $value
     * @param mixed $rule
     * @return boolean
     */
    public function equal($value, $rule)
    {
        return $value == $rule;
    }

    /**
     * 验证是否不等于某个值
     * @param string $value
     * @param mixed $rule 
     * @return boolean
     */
    public function notEqual($value, $rule)
    {
        return !$this->equal($value, $rule);
    }

    /**
     * 验证是否在某个指定范围之内
     * @param string $value
     * @param string|array $rule 字符串格式以,分隔
     * @return boolean
     */
    public function in($value, $rule)
    {
        return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
    }

    /**
     * 验证是否在某个指定范围之外
     * @param string $value
     * @param string|array $rule 字符串格式以,分隔
     * @return boolean
     */
    public function notIn($value, $rule)
    {
        return !$this->in($value, $rule);
    }

    /**
     * 验证是否在某个数值范围
     * @param string $value
     * @param string|array $rule 字符串格式以,分隔
     * @return boolean
     */
    public function between($value, $rule)
    {
        if (is_array($rule)) {
            list($min, $max) = $rule;
        } else {
            list($min, $max) = explode(',', $rule);
        }
        return $value >= $min && $value <= $max;
    }

    /**
     * 验证是否不在某个数值范围
     * @param string $value
     * @param string|array $rule 字符串格式以,分隔
     * @return boolean
     */
    public function notBetween($value, $rule)
    {
        return !$this->between($value, $rule);
    }

    /**
     * 当前时间是否过期了
     * @param string $value
     * @param string $rule 以,分隔
     * @return boolean
     */
    public function expired($value, $rule)
    {
        list($start, $end) = explode(',', $rule);
        if (!is_numeric($start)) {
            $start = strtotime($start);
        }

        if (!is_numeric($end)) {
            $end = strtotime($end);
        }

        return NOW_TIME >= $start && NOW_TIME <= $end;
    }

    /**
     * 
     * @param string $value
     * @param string $rule
     * @return boolean
     */
    public function allowIP($value, $rule)
    {
        return in_array(get_client_ip(), explode(',', $rule));
    }

    /**
     * 
     * @param string $value
     * @param string $rule
     * @return boolean
     */
    public function denyIP($value, $rule)
    {
        return !$this->allowIP($value, $rule);
    }

    /**
     * 使用正则验证数据
     *
     * @param string $value  要验证的数据
     * @param string $rule 验证规则
     * @return boolean
     */
    public function regex($value, $rule)
    {
        // 检查是否有内置的正则表达式
        if (isset(self::$builtinRegexes[strtolower($rule)])) {
            $rule = self::$builtinRegexes[strtolower($rule)];
        }

        return preg_match($rule, $value) === 1;
    }
    
    public function email($value)
    {
        return $this->regex($value, static::EMAIL);
    }
    
    public function url($value)
    {
        return $this->regex($value, static::URL);
    }
    
    public function mobile($value)
    {
        return $this->regex($value, static::MOBILE);
    }

}
